/*
 * $Id: EditorActions.java,v 1.37 2012-01-13 11:36:35 david Exp $
 * Copyright (c) 2001-2012, JGraph Ltd
 */
package com.kg.rails.ui.editor;

import com.kg.rails.component.editor.analysis.DistanceCostFunction;
import com.kg.rails.component.editor.analysis.GraphAnalysis;
import com.kg.rails.component.editor.canvas.Graphics2DCanvas;
import com.kg.rails.component.editor.io.Codec;
import com.kg.rails.component.editor.model.Cell;
import com.kg.rails.component.editor.model.IGraphModel;
import com.kg.rails.component.editor.shape.StencilShape;
import com.kg.rails.component.editor.swing.GraphComponent;
import com.kg.rails.component.editor.swing.GraphOutline;
import com.kg.rails.component.editor.swing.handler.ConnectionHandler;
import com.kg.rails.component.editor.swing.util.GraphActions;
import com.kg.rails.component.editor.swing.view.mxCellEditor;
import com.kg.rails.component.editor.util.*;
import com.kg.rails.component.editor.util.png.mxPngEncodeParam;
import com.kg.rails.component.editor.util.png.mxPngImageEncoder;
import com.kg.rails.component.editor.view.Graph;
import org.w3c.dom.Document;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URLEncoder;

/**
 *
 */
public class EditorActions {
    /**
     * @param e
     * @return Returns the graph for the given action event.
     */
    public static final BasicGraphEditor getEditor(ActionEvent e) {
        if (e.getSource() instanceof Component) {
            Component component = (Component) e.getSource();

            while (component != null
                    && !(component instanceof BasicGraphEditor)) {
                component = component.getParent();
            }

            return (BasicGraphEditor) component;
        }

        return null;
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ToggleRulersItem extends JCheckBoxMenuItem {
        /**
         *
         */
        public ToggleRulersItem(final BasicGraphEditor editor, String name) {
            super(name);
            setSelected(editor.getGraphComponent().getColumnHeader() != null);

            addActionListener(new ActionListener() {
                /**
                 *
                 */
                public void actionPerformed(ActionEvent e) {
                    GraphComponent graphComponent = editor
                            .getGraphComponent();

                    if (graphComponent.getColumnHeader() != null) {
                        graphComponent.setColumnHeader(null);
                        graphComponent.setRowHeader(null);
                    } else {
                        graphComponent.setColumnHeaderView(new EditorRuler(
                                graphComponent,
                                EditorRuler.ORIENTATION_HORIZONTAL));
                        graphComponent.setRowHeaderView(new EditorRuler(
                                graphComponent,
                                EditorRuler.ORIENTATION_VERTICAL));
                    }
                }
            });
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ToggleGridItem extends JCheckBoxMenuItem {
        /**
         *
         */
        public ToggleGridItem(final BasicGraphEditor editor, String name) {
            super(name);
            setSelected(true);

            addActionListener(new ActionListener() {
                /**
                 *
                 */
                public void actionPerformed(ActionEvent e) {
                    GraphComponent graphComponent = editor
                            .getGraphComponent();
                    Graph graph = graphComponent.getGraph();
                    boolean enabled = !graph.isGridEnabled();

                    graph.setGridEnabled(enabled);
                    graphComponent.setGridVisible(enabled);
                    graphComponent.repaint();
                    setSelected(enabled);
                }
            });
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ToggleOutlineItem extends JCheckBoxMenuItem {
        /**
         *
         */
        public ToggleOutlineItem(final BasicGraphEditor editor, String name) {
            super(name);
            setSelected(true);

            addActionListener(new ActionListener() {
                /**
                 *
                 */
                public void actionPerformed(ActionEvent e) {
                    final GraphOutline outline = editor.getGraphOutline();
                    outline.setVisible(!outline.isVisible());
                    outline.revalidate();

                    SwingUtilities.invokeLater(new Runnable() {
                        /*
                               * (non-Javadoc)
                               * @see java.lang.Runnable#run()
                               */
                        public void run() {
                            if (outline.getParent() instanceof JSplitPane) {
                                if (outline.isVisible()) {
                                    ((JSplitPane) outline.getParent())
                                            .setDividerLocation(editor
                                                    .getHeight() - 300);
                                    ((JSplitPane) outline.getParent())
                                            .setDividerSize(6);
                                } else {
                                    ((JSplitPane) outline.getParent())
                                            .setDividerSize(0);
                                }
                            }
                        }
                    });
                }
            });
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ExitAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            BasicGraphEditor editor = getEditor(e);

            if (editor != null) {
                editor.exit();
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class StylesheetAction extends AbstractAction {
        /**
         *
         */
        protected String stylesheet;

        /**
         *
         */
        public StylesheetAction(String stylesheet) {
            this.stylesheet = stylesheet;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Graph graph = graphComponent.getGraph();
                Codec codec = new Codec();
                Document doc = EditorUtils.loadDocument(EditorActions.class
                        .getResource(stylesheet).toString());

                if (doc != null) {
                    codec.decode(doc.getDocumentElement(),
                            graph.getStylesheet());
                    graph.refresh();
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ZoomPolicyAction extends AbstractAction {
        /**
         *
         */
        protected int zoomPolicy;

        /**
         *
         */
        public ZoomPolicyAction(int zoomPolicy) {
            this.zoomPolicy = zoomPolicy;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                graphComponent.setPageVisible(true);
                graphComponent.setZoomPolicy(zoomPolicy);
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class GridStyleAction extends AbstractAction {
        protected int style;

        public GridStyleAction(int style) {
            this.style = style;
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                graphComponent.setGridStyle(style);
                graphComponent.repaint();
            }
        }
    }

    @SuppressWarnings("serial")
    public static class GridColorAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Color newColor = JColorChooser.showDialog(graphComponent,
                        Resources.get("gridColor"),
                        graphComponent.getGridColor());

                if (newColor != null) {
                    graphComponent.setGridColor(newColor);
                    graphComponent.repaint();
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class ScaleAction extends AbstractAction {
        protected double scale;

        public ScaleAction(double scale) {
            this.scale = scale;
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                double scale = this.scale;

                if (scale == 0) {
                    String value = (String) JOptionPane.showInputDialog(
                            graphComponent, Resources.get("value"),
                            Resources.get("scale") + " (%)",
                            JOptionPane.PLAIN_MESSAGE, null, null, "");

                    if (value != null) {
                        scale = Double.parseDouble(value.replace("%", "")) / 100;
                    }
                }

                if (scale > 0) {
                    graphComponent.zoomTo(scale, graphComponent.isCenterZoom());
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class PageSetupAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                PrinterJob pj = PrinterJob.getPrinterJob();
                PageFormat format = pj.pageDialog(graphComponent
                        .getPageFormat());

                if (format != null) {
                    graphComponent.setPageFormat(format);
                    graphComponent.zoomAndCenter();
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class PrintAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                PrinterJob pj = PrinterJob.getPrinterJob();

                if (pj.printDialog()) {
                    PageFormat pf = graphComponent.getPageFormat();
                    Paper paper = new Paper();
                    double margin = 36;
                    paper.setImageableArea(margin, margin, paper.getWidth()
                            - margin * 2, paper.getHeight() - margin * 2);
                    pf.setPaper(paper);
                    pj.setPrintable(graphComponent, pf);

                    try {
                        pj.print();
                    } catch (PrinterException e2) {
                        System.out.println(e2);
                    }
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class SaveAction extends AbstractAction {
        protected boolean showDialog;
        protected String lastDir = null;

        public SaveAction(boolean showDialog) {
            this.showDialog = showDialog;
        }

        public void actionPerformed(ActionEvent e) {
            BasicGraphEditor editor = getEditor(e);

            if (editor != null) {
                GraphComponent graphComponent = editor.getGraphComponent();
                Graph graph = graphComponent.getGraph();
                FileFilter selectedFilter = null;
                String filename = null;
                boolean dialogShown = false;

                if (showDialog || editor.getCurrentFile() == null) {
                    String wd;

                    if (lastDir != null) {
                        wd = lastDir;
                    } else if (editor.getCurrentFile() != null) {
                        wd = editor.getCurrentFile().getParent();
                    } else {
                        wd = System.getProperty("user.dir");
                    }

                    JFileChooser fc = new JFileChooser(wd);
                    fc.addChoosableFileFilter(new DefaultFileFilter(".ri",
                            "Файл железнодорожной инфраструктуры " + Resources.get("file")
                                    + " (.ri)"));

                    int rc = fc.showDialog(null, Resources.get("save"));
                    dialogShown = true;

                    if (rc != JFileChooser.APPROVE_OPTION) {
                        return;
                    } else {
                        lastDir = fc.getSelectedFile().getParent();
                    }

                    filename = fc.getSelectedFile().getAbsolutePath();
                    selectedFilter = fc.getFileFilter();

                    if (selectedFilter instanceof DefaultFileFilter) {
                        String ext = ((DefaultFileFilter) selectedFilter)
                                .getExtension();

                        if (!filename.toLowerCase().endsWith(ext)) {
                            filename += ext;
                        }
                    }

                    if (new File(filename).exists()
                            && JOptionPane.showConfirmDialog(graphComponent,
                            Resources.get("overwriteExistingFile")) != JOptionPane.YES_OPTION) {
                        return;
                    }
                } else {
                    filename = editor.getCurrentFile().getAbsolutePath();
                }

                try {
                    Codec codec = new Codec();
                    String xml = XmlUtils.getXml(codec.encode(graph.getModel()));

                    EditorUtils.writeFile(xml, filename);

                    editor.setModified(false);
                    editor.setCurrentFile(new File(filename));
                } catch (Throwable ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(graphComponent,
                            ex.toString(), Resources.get("error"),
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class SelectShortestPathAction extends AbstractAction {
        protected boolean directed;

        public SelectShortestPathAction(boolean directed) {
            this.directed = directed;
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Graph graph = graphComponent.getGraph();
                IGraphModel model = graph.getModel();

                Object source = null;
                Object target = null;

                Object[] cells = graph.getSelectionCells();

                for (Object cell : cells) {
                    if (model.isVertex(cell)) {
                        if (source == null) {
                            source = cell;
                        } else if (target == null) {
                            target = cell;
                        }
                    }

                    if (source != null && target != null) {
                        break;
                    }
                }

                if (source != null && target != null) {
                    int steps = graph.getChildEdges(graph.getDefaultParent()).length;
                    Object[] path = GraphAnalysis.getInstance()
                            .getShortestPath(graph, source, target,
                                    new DistanceCostFunction(), steps,
                                    directed);
                    graph.setSelectionCells(path);
                } else {
                    JOptionPane.showMessageDialog(graphComponent,
                            Resources.get("noSourceAndTargetSelected"));
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class SelectSpanningTreeAction extends AbstractAction {
        /**
         *
         */
        protected boolean directed;

        /**
         *
         */
        public SelectSpanningTreeAction(boolean directed) {
            this.directed = directed;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Graph graph = graphComponent.getGraph();
                IGraphModel model = graph.getModel();

                Object parent = graph.getDefaultParent();
                Object[] cells = graph.getSelectionCells();

                for (int i = 0; i < cells.length; i++) {
                    if (model.getChildCount(cells[i]) > 0) {
                        parent = cells[i];
                        break;
                    }
                }

                Object[] v = graph.getChildVertices(parent);
                Object[] mst = GraphAnalysis.getInstance()
                        .getMinimumSpanningTree(graph, v,
                                new DistanceCostFunction(), directed);
                graph.setSelectionCells(mst);
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ToggleDirtyAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                graphComponent.showDirtyRectangle = !graphComponent.showDirtyRectangle;
            }
        }

    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ToggleConnectModeAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                ConnectionHandler handler = graphComponent
                        .getConnectionHandler();
                handler.setHandleEnabled(!handler.isHandleEnabled());
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ToggleCreateTargetItem extends JCheckBoxMenuItem {
        /**
         *
         */
        public ToggleCreateTargetItem(final BasicGraphEditor editor, String name) {
            super(name);
            setSelected(true);

            addActionListener(new ActionListener() {
                /**
                 *
                 */
                public void actionPerformed(ActionEvent e) {
                    GraphComponent graphComponent = editor
                            .getGraphComponent();

                    if (graphComponent != null) {
                        ConnectionHandler handler = graphComponent
                                .getConnectionHandler();
                        handler.setCreateTarget(!handler.isCreateTarget());
                        setSelected(handler.isCreateTarget());
                    }
                }
            });
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class PromptPropertyAction extends AbstractAction {
        /**
         *
         */
        protected Object target;

        /**
         *
         */
        protected String fieldname, message;

        /**
         *
         */
        public PromptPropertyAction(Object target, String message) {
            this(target, message, message);
        }

        /**
         *
         */
        public PromptPropertyAction(Object target, String message,
                                    String fieldname) {
            this.target = target;
            this.message = message;
            this.fieldname = fieldname;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof Component) {
                try {
                    Method getter = target.getClass().getMethod(
                            "get" + fieldname);
                    Object current = getter.invoke(target);

                    // TODO: Support other atomic types
                    if (current instanceof Integer) {
                        Method setter = target.getClass().getMethod(
                                "set" + fieldname, new Class[]{int.class});

                        String value = (String) JOptionPane.showInputDialog(
                                (Component) e.getSource(), "Value", message,
                                JOptionPane.PLAIN_MESSAGE, null, null, current);

                        if (value != null) {
                            setter.invoke(target, Integer.parseInt(value));
                        }
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }

            // Repaints the graph component
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                graphComponent.repaint();
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class TogglePropertyItem extends JCheckBoxMenuItem {
        /**
         *
         */
        public TogglePropertyItem(Object target, String name, String fieldname) {
            this(target, name, fieldname, false);
        }

        /**
         *
         */
        public TogglePropertyItem(Object target, String name, String fieldname,
                                  boolean refresh) {
            this(target, name, fieldname, refresh, null);
        }

        /**
         *
         */
        public TogglePropertyItem(final Object target, String name,
                                  final String fieldname, final boolean refresh,
                                  ActionListener listener) {
            super(name);

            // Since action listeners are processed last to first we add the given
            // listener here which means it will be processed after the one below
            if (listener != null) {
                addActionListener(listener);
            }

            addActionListener(new ActionListener() {
                /**
                 *
                 */
                public void actionPerformed(ActionEvent e) {
                    execute(target, fieldname, refresh);
                }
            });

            PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {

                /*
                     * (non-Javadoc)
                     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
                     */
                public void propertyChange(PropertyChangeEvent evt) {
                    if (evt.getPropertyName().equalsIgnoreCase(fieldname)) {
                        update(target, fieldname);
                    }
                }
            };

            if (target instanceof GraphComponent) {
                ((GraphComponent) target)
                        .addPropertyChangeListener(propertyChangeListener);
            } else if (target instanceof Graph) {
                ((Graph) target)
                        .addPropertyChangeListener(propertyChangeListener);
            }

            update(target, fieldname);
        }

        /**
         *
         */
        public void update(Object target, String fieldname) {
            if (target != null && fieldname != null) {
                try {
                    Method getter = target.getClass().getMethod(
                            "is" + fieldname);

                    if (getter != null) {
                        Object current = getter.invoke(target);

                        if (current instanceof Boolean) {
                            setSelected(((Boolean) current).booleanValue());
                        }
                    }
                } catch (Exception e) {
                    // ignore
                }
            }
        }

        /**
         *
         */
        public void execute(Object target, String fieldname, boolean refresh) {
            if (target != null && fieldname != null) {
                try {
                    Method getter = target.getClass().getMethod(
                            "is" + fieldname);
                    Method setter = target.getClass().getMethod(
                            "set" + fieldname, new Class[]{boolean.class});

                    Object current = getter.invoke(target);

                    if (current instanceof Boolean) {
                        boolean value = !((Boolean) current).booleanValue();
                        setter.invoke(target, value);
                        setSelected(value);
                    }

                    if (refresh) {
                        Graph graph = null;

                        if (target instanceof Graph) {
                            graph = (Graph) target;
                        } else if (target instanceof GraphComponent) {
                            graph = ((GraphComponent) target).getGraph();
                        }

                        graph.refresh();
                    }
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class HistoryAction extends AbstractAction {
        /**
         *
         */
        protected boolean undo;

        /**
         *
         */
        public HistoryAction(boolean undo) {
            this.undo = undo;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            BasicGraphEditor editor = getEditor(e);

            if (editor != null) {
                if (undo) {
                    editor.getUndoManager().undo();
                } else {
                    editor.getUndoManager().redo();
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class FontStyleAction extends AbstractAction {
        /**
         *
         */
        protected boolean bold;

        /**
         *
         */
        public FontStyleAction(boolean bold) {
            this.bold = bold;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Component editorComponent = null;

                if (graphComponent.getCellEditor() instanceof mxCellEditor) {
                    editorComponent = ((mxCellEditor) graphComponent
                            .getCellEditor()).getEditor();
                }

                if (editorComponent instanceof JEditorPane) {
                    JEditorPane editorPane = (JEditorPane) editorComponent;
                    int start = editorPane.getSelectionStart();
                    int ende = editorPane.getSelectionEnd();
                    String text = editorPane.getSelectedText();

                    if (text == null) {
                        text = "";
                    }

                    try {
                        HTMLEditorKit editorKit = new HTMLEditorKit();
                        HTMLDocument document = (HTMLDocument) editorPane
                                .getDocument();
                        document.remove(start, (ende - start));
                        editorKit.insertHTML(document, start, ((bold) ? "<b>"
                                : "<i>") + text + ((bold) ? "</b>" : "</i>"),
                                0, 0, (bold) ? HTML.Tag.B : HTML.Tag.I);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }

                    editorPane.requestFocus();
                    editorPane.select(start, ende);
                } else {
                    IGraphModel model = graphComponent.getGraph().getModel();
                    model.beginUpdate();
                    try {
                        graphComponent.stopEditing(false);
                        graphComponent.getGraph().toggleCellStyleFlags(
                                Constants.STYLE_FONTSTYLE,
                                (bold) ? Constants.FONT_BOLD
                                        : Constants.FONT_ITALIC);
                    } finally {
                        model.endUpdate();
                    }
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class WarningAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Object[] cells = graphComponent.getGraph().getSelectionCells();

                if (cells != null && cells.length > 0) {
                    String warning = JOptionPane.showInputDialog(Resources
                            .get("enterWarningMessage"));

                    for (int i = 0; i < cells.length; i++) {
                        graphComponent.setCellWarning(cells[i], warning);
                    }
                } else {
                    JOptionPane.showMessageDialog(graphComponent,
                            Resources.get("noCellSelected"));
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class NewAction extends AbstractAction {
        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            BasicGraphEditor editor = getEditor(e);

            if (editor != null) {
                if (!editor.isModified()
                        || JOptionPane.showConfirmDialog(editor,
                        Resources.get("loseChanges")) == JOptionPane.YES_OPTION) {
                    Graph graph = editor.getGraphComponent().getGraph();

                    // Check modified flag and display save dialog
                    Cell root = new Cell();
                    root.insert(new Cell());
                    graph.getModel().setRoot(root);

                    editor.setModified(false);
                    editor.setCurrentFile(null);
                    editor.getGraphComponent().zoomAndCenter();
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class ImportAction extends AbstractAction {
        /**
         *
         */
        protected String lastDir;

        /**
         * Loads and registers the shape as a new shape in Graphics2DCanvas and
         * adds a new entry to use that shape in the specified palette
         *
         * @param palette The palette to add the shape to.
         * @param nodeXml The raw XML of the shape
         * @param path    The path to the directory the shape exists in
         * @return the string name of the shape
         */
        public static String addStencilShape(EditorPalette palette,
                                             String nodeXml, String path) {

            // Some editors place a 3 byte BOM at the start of files
            // Ensure the first char is a "<"
            int lessthanIndex = nodeXml.indexOf("<");
            nodeXml = nodeXml.substring(lessthanIndex);
            StencilShape newShape = new StencilShape(nodeXml);
            String name = newShape.getName();
            ImageIcon icon = null;

            if (path != null) {
                String iconPath = path + newShape.getIconPath();
                icon = new ImageIcon(iconPath);
            }

            // Registers the shape in the canvas shape registry
            Graphics2DCanvas.putShape(name, newShape);

            if (palette != null && icon != null) {
                palette.addTemplate(name, icon, "shape=" + name, 80, 80, "");
            }

            return name;
        }

        /**
         *
         */
        public void actionPerformed(ActionEvent e) {
            BasicGraphEditor editor = getEditor(e);

            if (editor != null) {
                String wd = (lastDir != null) ? lastDir : System
                        .getProperty("user.dir");

                JFileChooser fc = new JFileChooser(wd);

                fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

                // Adds file filter for Dia shape import
                fc.addChoosableFileFilter(new DefaultFileFilter(".shape",
                        "Dia Shape " + Resources.get("file") + " (.shape)"));

                int rc = fc.showDialog(null, Resources.get("importStencil"));

                if (rc == JFileChooser.APPROVE_OPTION) {
                    lastDir = fc.getSelectedFile().getParent();

                    try {
                        if (fc.getSelectedFile().isDirectory()) {
                            EditorPalette palette = editor.insertPalette(fc
                                    .getSelectedFile().getName());

                            for (File f : fc.getSelectedFile().listFiles(
                                    new FilenameFilter() {
                                        public boolean accept(File dir,
                                                              String name) {
                                            return name.toLowerCase().endsWith(
                                                    ".shape");
                                        }
                                    })) {
                                String nodeXml = EditorUtils.readFile(f
                                        .getAbsolutePath());
                                addStencilShape(palette, nodeXml, f.getParent()
                                        + File.separator);
                            }

                            JComponent scrollPane = (JComponent) palette
                                    .getParent().getParent();
                            editor.getLibraryPane().setSelectedComponent(
                                    scrollPane);

                            // FIXME: Need to update the size of the palette to force a layout
                            // update. Re/in/validate of palette or parent does not work.
                            //editor.getLibraryPane().revalidate();
                        } else {
                            String nodeXml = EditorUtils.readFile(fc
                                    .getSelectedFile().getAbsolutePath());
                            String name = addStencilShape(null, nodeXml, null);

                            JOptionPane.showMessageDialog(editor, Resources
                                    .get("stencilImported",
                                            new String[]{name}));
                        }
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class OpenAction extends AbstractAction {
        protected String lastDir;

        protected void resetEditor(BasicGraphEditor editor) {
            editor.setModified(false);
            editor.getUndoManager().clear();
            editor.getGraphComponent().zoomAndCenter();
        }

        public void actionPerformed(ActionEvent e) {
            BasicGraphEditor editor = getEditor(e);

            if (editor != null) {
                if (!editor.isModified() || JOptionPane.showConfirmDialog(editor, Resources.get("loseChanges")) == JOptionPane.YES_OPTION) {
                    Graph graph = editor.getGraphComponent().getGraph();

                    if (graph != null) {
                        String wd = (lastDir != null) ? lastDir : System.getProperty("user.dir");

                        JFileChooser fc = new JFileChooser(wd);
                        fc.addChoosableFileFilter(new DefaultFileFilter(".ri",
                                "Файл железнодорожной инфраструктуры " + Resources.get("file")
                                        + " (.ri)"));

                        int rc = fc.showDialog(null, Resources.get("openFile"));

                        if (rc == JFileChooser.APPROVE_OPTION) {
                            lastDir = fc.getSelectedFile().getParent();

                            try {
                                Document document = XmlUtils.parseXml(EditorUtils.readFile(fc.getSelectedFile().getAbsolutePath()));

                                Codec codec = new Codec(document);
                                codec.decode(document.getDocumentElement(), graph.getModel());
                                editor.setCurrentFile(fc.getSelectedFile());

                                resetEditor(editor);
                            } catch (IOException ex) {
                                ex.printStackTrace();
                                JOptionPane.showMessageDialog(editor.getGraphComponent(), ex.toString(), Resources.get("error"), JOptionPane.ERROR_MESSAGE);
                            }
                        }
                    }
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class ToggleAction extends AbstractAction {
        protected String key;
        protected boolean defaultValue;

        public ToggleAction(String key) {
            this(key, false);
        }

        public ToggleAction(String key, boolean defaultValue) {
            this.key = key;
            this.defaultValue = defaultValue;
        }

        public void actionPerformed(ActionEvent e) {
            Graph graph = GraphActions.getGraph(e);

            if (graph != null) {
                graph.toggleCellStyles(key, defaultValue);
            }
        }
    }

    @SuppressWarnings("serial")
    public static class SetLabelPositionAction extends AbstractAction {
        protected String labelPosition, alignment;

        public SetLabelPositionAction(String labelPosition, String alignment) {
            this.labelPosition = labelPosition;
            this.alignment = alignment;
        }

        public void actionPerformed(ActionEvent e) {
            Graph graph = GraphActions.getGraph(e);

            if (graph != null && !graph.isSelectionEmpty()) {
                graph.getModel().beginUpdate();
                try {
                    // Checks the orientation of the alignment to use the correct constants
                    if (labelPosition.equals(Constants.ALIGN_LEFT)
                            || labelPosition.equals(Constants.ALIGN_CENTER)
                            || labelPosition.equals(Constants.ALIGN_RIGHT)) {
                        graph.setCellStyles(Constants.STYLE_LABEL_POSITION,
                                labelPosition);
                        graph.setCellStyles(Constants.STYLE_ALIGN, alignment);
                    } else {
                        graph.setCellStyles(
                                Constants.STYLE_VERTICAL_LABEL_POSITION,
                                labelPosition);
                        graph.setCellStyles(Constants.STYLE_VERTICAL_ALIGN,
                                alignment);
                    }
                } finally {
                    graph.getModel().endUpdate();
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class SetStyleAction extends AbstractAction {
        protected String value;

        public SetStyleAction(String value) {
            this.value = value;
        }

        public void actionPerformed(ActionEvent e) {
            Graph graph = GraphActions.getGraph(e);

            if (graph != null && !graph.isSelectionEmpty()) {
                graph.setCellStyle(value);
            }
        }
    }

    @SuppressWarnings("serial")
    public static class KeyValueAction extends AbstractAction {
        protected String key, value;

        public KeyValueAction(String key) {
            this(key, null);
        }

        public KeyValueAction(String key, String value) {
            this.key = key;
            this.value = value;
        }

        public void actionPerformed(ActionEvent e) {
            Graph graph = GraphActions.getGraph(e);

            if (graph != null && !graph.isSelectionEmpty()) {
                graph.setCellStyles(key, value);
            }
        }
    }

    @SuppressWarnings("serial")
    public static class PromptValueAction extends AbstractAction {
        protected String key, message;

        public PromptValueAction(String key, String message) {
            this.key = key;
            this.message = message;
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof Component) {
                Graph graph = GraphActions.getGraph(e);

                if (graph != null && !graph.isSelectionEmpty()) {
                    String value = (String) JOptionPane.showInputDialog(
                            (Component) e.getSource(),
                            Resources.get("value"), message,
                            JOptionPane.PLAIN_MESSAGE, null, null, "");

                    if (value != null) {
                        if (value.equals(Constants.NONE)) {
                            value = null;
                        }

                        graph.setCellStyles(key, value);
                    }
                }
            }
        }
    }

    /**
     *
     */
    @SuppressWarnings("serial")
    public static class AlignCellsAction extends AbstractAction {
        protected String align;

        public AlignCellsAction(String align) {
            this.align = align;
        }

        public void actionPerformed(ActionEvent e) {
            Graph graph = GraphActions.getGraph(e);

            if (graph != null && !graph.isSelectionEmpty()) {
                graph.alignCells(align);
            }
        }
    }

    @SuppressWarnings("serial")
    public static class AutosizeAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            Graph graph = GraphActions.getGraph(e);

            if (graph != null && !graph.isSelectionEmpty()) {
                Object[] cells = graph.getSelectionCells();
                IGraphModel model = graph.getModel();

                model.beginUpdate();
                try {
                    for (int i = 0; i < cells.length; i++) {
                        graph.updateCellSize(cells[i]);
                    }
                } finally {
                    model.endUpdate();
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class ColorAction extends AbstractAction {
        protected String name, key;

        public ColorAction(String name, String key) {
            this.name = name;
            this.key = key;
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Graph graph = graphComponent.getGraph();

                if (!graph.isSelectionEmpty()) {
                    Color newColor = JColorChooser.showDialog(graphComponent,
                            name, null);

                    if (newColor != null) {
                        graph.setCellStyles(key, EditorUtils.hexString(newColor));
                    }
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class BackgroundImageAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                String value = (String) JOptionPane.showInputDialog(
                        graphComponent, Resources.get("backgroundImage"),
                        "URL", JOptionPane.PLAIN_MESSAGE, null, null,
                        "http://www.callatecs.com/images/background2.JPG");

                if (value != null) {
                    if (value.length() == 0) {
                        graphComponent.setBackgroundImage(null);
                    } else {
                        Image background = EditorUtils.loadImage(value);
                        // Incorrect URLs will result in no image.
                        // TODO provide feedback that the URL is not correct
                        if (background != null) {
                            graphComponent.setBackgroundImage(new ImageIcon(
                                    background));
                        }
                    }

                    // Forces a repaint of the outline
                    graphComponent.getGraph().repaint();
                }
            }
        }
    }

    @SuppressWarnings("serial")
    public static class BackgroundAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Color newColor = JColorChooser.showDialog(graphComponent,
                        Resources.get("background"), null);

                if (newColor != null) {
                    graphComponent.getViewport().setOpaque(true);
                    graphComponent.getViewport().setBackground(newColor);
                }

                // Forces a repaint of the outline
                graphComponent.getGraph().repaint();
            }
        }
    }

    @SuppressWarnings("serial")
    public static class PageBackgroundAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Color newColor = JColorChooser.showDialog(graphComponent,
                        Resources.get("pageBackground"), null);

                if (newColor != null) {
                    graphComponent.setPageBackgroundColor(newColor);
                }

                // Forces a repaint of the component
                graphComponent.repaint();
            }
        }
    }

    @SuppressWarnings("serial")
    public static class StyleAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() instanceof GraphComponent) {
                GraphComponent graphComponent = (GraphComponent) e
                        .getSource();
                Graph graph = graphComponent.getGraph();
                String initial = graph.getModel().getStyle(
                        graph.getSelectionCell());
                String value = (String) JOptionPane.showInputDialog(
                        graphComponent, Resources.get("style"),
                        Resources.get("style"), JOptionPane.PLAIN_MESSAGE,
                        null, null, initial);

                if (value != null) {
                    graph.setCellStyle(value);
                }
            }
        }
    }
}
